package com.ldy.core.web;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.hutool.core.lang.UUID;
/**
 * 支持断点下载
 * <pre>
    @RequestMapping("/download")
	public void download(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO 获取下载的包，根据业务自行处理
		File file = new File("../app.apk");
		// 下载
		DownloadUtil.download(request, response, file);
	}
 * </pre>
 *
 */
public class DownloadUtil {
	
	public static void main(String[] args) {
		
		
		System.out.println(UUID.randomUUID().toString());
	}
	
	
	private static final char[] hexCode = "0123456789ABCDEF".toCharArray();

	/**
	 * 下载（断点下载、分块下载）
	* @param request
	* @param response
	* @param file
	* @throws Exception
	 */
	public static void download(HttpServletRequest request, HttpServletResponse response, File file) throws Exception {
		try (ServletOutputStream out = response.getOutputStream();  // 
				InputStream in = new FileInputStream(file)) {
			// 获取断点下载标记
			String range = request.getHeader("Range");
			// response.reset();
			String mimeType = request.getServletContext().getMimeType(file.getName());
			response.setContentType(null != mimeType ? mimeType : "application/octet-stream; charset=UTF-8");
			// 自定义文件名
			response.setHeader("Content-Disposition", String.format("attachment; filename=%s", file.getName()));
			response.setHeader("Accept-Ranges", "bytes");
			// TODO 文件签名，文件hash，保证下载一致（架构存在PackageMD5头也能用）
			response.setHeader("ETag", fileMD5(file));
			// 获取数据流总大小
			long length = file.length();
			// 定义有效数据流起始, 截止位置
			long from = 0, to = length - 1;
			if (null == range) {
				// 设置文件大小
				response.setHeader("Content-Length", Long.toString(length));
			} else {
				// 续传206
				response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
				String[] ranges = range.replace("bytes=", "").split("-");
				if (ranges.length > 0) {					
					from = Long.parseLong(ranges[0]);
				}
				if (ranges.length > 1) {					
					to = Long.parseLong(ranges[1]);
				}
				// 设置本批次数据大小
				response.setHeader("Content-Length", Long.toString(to - from + 1L));
				// 设置本批次数据范围及数据总大小
				response.setHeader("Content-Range", String.format("bytes %d-%d/%d", from, to, length));
			}
			// 设置本次起始位置
			in.skip(from);
			// 限制数据流大小, 最大等于文件大小
			long limit = to - from + 1;
			// 缓冲大小
			int bufferSize = (int) (limit > 2048 ? 2048 : limit);
			// 创建缓冲数组
			byte[] buffer = new byte[bufferSize];
			int num = 0;
			while (0 < limit && (num = in.read(buffer)) != -1) {
				out.write(buffer, 0, num);
				limit -= num;
				if (limit < bufferSize) {
					buffer = new byte[(int) limit];
				}
			}
			response.flushBuffer();
		}
	}


	/**
	 * 文件 MD5
	 * 
	 * @param file
	 * @return md5
	 */
	private static String fileMD5(File file) {
		try (InputStream stream = Files.newInputStream(file.toPath(), StandardOpenOption.READ)) {
			MessageDigest digest = MessageDigest.getInstance("MD5");
			byte[] buf = new byte[8192];
			int len;
			while ((len = stream.read(buf)) > 0) {
				digest.update(buf, 0, len);
			}
			return toHexString(digest.digest());
		} catch (IOException e) {
			e.printStackTrace();
			return "";
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return "";
		}
	}

	/**
	 * Hex
	 * 
	 * @param data
	 * @return
	 */
	private static String toHexString(byte[] data) {
		StringBuilder r = new StringBuilder(data.length * 2);
		for (byte b : data) {
			r.append(hexCode[(b >> 4) & 0xF]);
			r.append(hexCode[(b & 0xF)]);
		}
		return r.toString();
	}
}
